package cn.huaqingcheng.tianshu.security.web;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition;
import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPattern;

import java.lang.annotation.Annotation;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;

/**
 * FindRequestMapping
 */
@Slf4j
public record RequestMappingFind(
        RequestMappingHandlerMapping requestMappingHandlerMapping
) {

    /**
     * 查找含有指定注解的 {@link HandlerMethod}
     *
     * @return 含有指定注解的请求信息
     */
    public <T extends Annotation> Map<HttpMethod, Set<String>> find(Class<T> annotationType) {
        Map<HttpMethod, Set<String>> permitAllUrl = new ConcurrentHashMap<>();

        for (var entry : requestMappingHandlerMapping.getHandlerMethods().entrySet()) {
            RequestMappingInfo key = entry.getKey();
            HandlerMethod handlerMethod = entry.getValue();

            T annotation = Optional.ofNullable(AnnotationUtils.findAnnotation(handlerMethod.getMethod(), annotationType))
                    .orElseGet(() -> AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), annotationType));
            if (annotation == null) {
                continue;
            }

            checkError(key, annotationType);

            RequestMethodsRequestCondition methodsCondition = key.getMethodsCondition();

            PatternsRequestCondition patternsCondition = key.getPatternsCondition();
            if (patternsCondition != null) {
                Set<String> patterns = patternsCondition.getPatterns();
                for (RequestMethod method : methodsCondition.getMethods()) {
                    permitAllUrl.computeIfAbsent(convertMethod(method), _ -> new ConcurrentSkipListSet<>())
                            .addAll(patterns);
                }
            }

            PathPatternsRequestCondition pathPatternsCondition = key.getPathPatternsCondition();
            if (pathPatternsCondition != null) {
                var patterns = pathPatternsCondition.getPatterns()
                        .stream()
                        .map(PathPattern::getPatternString)
                        .filter(s -> !s.isEmpty())
                        .toList();
                for (RequestMethod method : methodsCondition.getMethods()) {
                    permitAllUrl.computeIfAbsent(convertMethod(method), _ -> new ConcurrentSkipListSet<>())
                            .addAll(patterns);
                }
            }
        }

        return permitAllUrl;
    }

    private static HttpMethod convertMethod(RequestMethod method) {
        return switch (method) {
            case GET -> HttpMethod.GET;
            case POST -> HttpMethod.POST;
            case PUT -> HttpMethod.PUT;
            case HEAD -> HttpMethod.HEAD;
            case DELETE -> HttpMethod.DELETE;
            case OPTIONS -> HttpMethod.OPTIONS;
            case PATCH -> HttpMethod.PATCH;
            case TRACE -> HttpMethod.TRACE;
        };
    }

    private static void checkError(RequestMappingInfo key, Class<? extends Annotation> annotationType) {
        HeadersRequestCondition headersCondition = key.getHeadersCondition();
        if (!headersCondition.getExpressions().isEmpty()) {
            log.error("{} 不能使用请求头匹配 {}", annotationType, headersCondition);
        }
        ParamsRequestCondition paramsCondition = key.getParamsCondition();
        if (!paramsCondition.getExpressions().isEmpty()) {
            log.error("{} 不能使用请求参数匹配 {}", annotationType, paramsCondition);
        }
        RequestCondition<?> customCondition = key.getCustomCondition();
        if (customCondition != null) {
            log.error("{} 不能使用自定义匹配 {}", annotationType, customCondition);
        }
    }

}
